<template>
  <div class="screen-wrapper">
    <div class="screen">
      <!-- 顶部栏（只展示） -->
      <header class="topbar">
        <div class="topbar-left">
          <div class="brand">
            MALL · 积分/提现/邀请 数据大屏
          </div>
          <div class="brand-left">
            <div
              class="env-pill"
              :title="envTip"
            >
              <span
                class="dot"
                :class="envClass"
              />
              {{ envLabel }}
            </div>
            <div class="topbar-center">
              <div class="clock">
                {{ nowStr }}
              </div>
            </div>
          </div>
        </div>

        <div class="topbar-right">
          <div class="kpis">
            <div class="kpi">
              <div class="kpi-label">
                积分余额（总）
              </div>
              <div class="kpi-value">
                {{ formatNum(pointsAgg.totalPoints) }}
              </div>
              <div class="kpi-sub">
                今日新增 +{{ formatNum(pointsAgg.todayAdded) }}
              </div>
            </div>

            <div class="kpi">
              <div class="kpi-label">
                可提现余额（总）
              </div>
              <div class="kpi-value">
                ¥{{ formatNum(cashAgg.withdrawable) }}
              </div>
              <div class="kpi-sub">
                待审核 ¥{{ formatNum(cashAgg.pendingAmount) }}
              </div>
            </div>

            <div class="kpi">
              <div class="kpi-label">
                邀请总览
              </div>
              <div class="kpi-value">
                L1 {{ inviteAgg.l1 }} / L2 {{ inviteAgg.l2 }} / L3 {{ inviteAgg.l3 }}
              </div>
              <div class="kpi-sub">
                团队今日积分 +{{ formatNum(inviteAgg.teamPointsToday) }}
              </div>
            </div>
          </div>
        </div>
      </header>

      <!-- 主体：纯展示，无交互入口 -->
      <main class="grid">
        <!-- 提现板块：趋势 + 渠道 + 状态 -->
        <section
          ref="withdrawTrendRef"
          class="panel span-2"
        >
          <div class="panel-title">
            提现趋势（最近30天）
          </div>
          <div class="chart" />
        </section>

        <section
          ref="withdrawMethodRef"
          class="panel"
        >
          <div class="panel-title">
            提现渠道占比（今日）
          </div>
          <div class="chart" />
        </section>

        <section
          ref="withdrawStatusRef"
          class="panel"
        >
          <div class="panel-title">
            提现状态分布（今日）
          </div>
          <div class="chart" />
        </section>

        <!-- 广告激励：曝光/完播/积分 + 渠道 + 漏斗 -->
        <section
          ref="adPointsTrendRef"
          class="panel span-2"
        >
          <div class="panel-title">
            广告激励（曝光 / 完播 / 积分，最近30天）
          </div>
          <div class="chart" />
        </section>

        <section
          ref="adChannelRef"
          class="panel"
        >
          <div class="panel-title">
            广告渠道分布（今日）
          </div>
          <div class="chart" />
        </section>

        <section
          ref="adFunnelRef"
          class="panel"
        >
          <div class="panel-title">
            今日转化漏斗：曝光 → 观看 → 完播 → 计积分
          </div>
          <div class="chart" />
        </section>

        <!-- 邀请体系：三级规模 + 7日漏斗 + 14日团队积分 -->
        <section
          ref="inviteStackRef"
          class="panel"
        >
          <div class="panel-title">
            三级邀请规模（累计）
          </div>
          <div class="chart" />
        </section>

        <section
          ref="inviteFunnelRef"
          class="panel"
        >
          <div class="panel-title">
            邀请转化漏斗（最近7天）
          </div>
          <div class="chart" />
        </section>

        <section
          ref="teamPointsRef"
          class="panel"
        >
          <div class="panel-title">
            团队积分（按层级，最近14天）
          </div>
          <div class="chart" />
        </section>

        <!-- 榜单：Top 邀请者 / 最近提现 / 广告积分日志 -->
        <section class="panel span-2">
          <div class="panel-title">
            Top 邀请者（按团队积分，7日）
          </div>
          <div class="table">
            <div class="thead">
              <span>#</span>
              <span>用户</span>
              <span>L1/L2/L3</span>
              <span>团队积分（7日）</span>
              <span>首购转化</span>
            </div>
            <div class="tbody">
              <div
                v-for="(u, i) in topInviters"
                :key="u.id"
                class="row"
              >
                <span>{{ i + 1 }}</span>
                <span class="sku">
                  {{ u.name }}
                </span>
                <span>{{ u.l1 }}/{{ u.l2 }}/{{ u.l3 }}</span>
                <span>{{ formatNum(u.teamPts7d) }}</span>
                <span>{{ (u.firstBuyRate * 100).toFixed(1) }}%</span>
              </div>
            </div>
          </div>
        </section>

        <section class="panel">
          <div class="panel-title">
            最近提现（20条）
          </div>
          <div class="table mini">
            <div class="thead">
              <span>用户</span>
              <span>金额</span>
              <span>渠道</span>
              <span>状态</span>
              <span>时间</span>
            </div>
            <div class="tbody">
              <div
                v-for="r in recentWithdraws"
                :key="r.id"
                class="row"
              >
                <span class="sku">
                  {{ r.user }}
                </span>
                <span>¥{{ formatNum(r.amount) }}</span>
                <span>{{ r.method }}</span>
                <span>{{ r.status }}</span>
                <span>{{ r.time }}</span>
              </div>
            </div>
          </div>
        </section>

        <section class="panel">
          <div class="panel-title">
            广告积分日志（20条）
          </div>
          <div class="table mini">
            <div class="thead">
              <span>用户</span>
              <span>积分</span>
              <span>渠道</span>
              <span>视频</span>
              <span>时间</span>
            </div>
            <div class="tbody">
              <div
                v-for="r in recentAdLogs"
                :key="r.id"
                class="row"
              >
                <span class="sku">
                  {{ r.user }}
                </span>
                <span>+{{ r.points }}</span>
                <span>{{ r.channel }}</span>
                <span>{{ r.videoId }}</span>
                <span>{{ r.time }}</span>
              </div>
            </div>
          </div>
        </section>
      </main>

      <footer class="bottombar">
        <div class="status">
          <span class="pill ok">
            API {{ apiHealthy ? '正常' : '异常' }}
          </span>
          <span class="pill warn">
            Redis {{ redisHealthy ? '正常' : '异常' }}
          </span>
          <span class="pill info">
            版本 {{ version }}
          </span>
        </div>
        <div class="copyright">
          © 2025 Mall4j 数据中台
        </div>
      </footer>
    </div>
  </div>
</template>

<script setup>
import {
  onMounted,
  onBeforeUnmount,
  ref,
  reactive,
  computed
} from 'vue'
import * as echarts from 'echarts'

/** ===== 主题与环境（只读） ===== */
const bg = 'radial-gradient(1200px 700px at 70% -10%, #14203d 0%, #0b1221 60%, #070d19 100%)'
const theme = ref(
  localStorage.getItem('mall_theme') || 'bluePurple'
)
const isFullscreen = ref(false)

function applyTheme () {
  const map = {
    bluePurple: 'radial-gradient(1200px 700px at 70% -10%, #14203d 0%, #0b1221 60%, #070d19 100%)',
    blueCyan: 'radial-gradient(1200px 700px at 70% -10%, #0f2433 0%, #081a22 60%, #05131b 100%)'
  }
  const bgv = map[theme.value] || map.bluePurple
  document.documentElement.style.setProperty(
    '--app-bg',
    bgv
  )
}

function onFsChange () {
  const d = document
  isFullscreen.value = !!d.fullscreenElement
}

/** ===== 顶部信息 ===== */
const nowStr = ref('')

function tickClock () {
  const d = new Date()
  const pad = (n) => String(n).padStart(2, '0')
  nowStr.value = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
}

const version = 'v1.0.2'
const env = (
  import.meta &&
  import.meta.env &&
  import.meta.env.MODE
) ? import.meta.env.MODE : 'prod'
const envMap = {
  dev: '开发',
  test: '测试',
  staging: '预发',
  prod: '生产'
}
const envLabel = envMap[env] || env
const envClass = computed(() => {
  const map = {
    dev: 'dev',
    test: 'test',
    staging: 'staging',
    prod: 'prod'
  }
  return map[env] || 'prod'
})
const envTip = '当前运行环境，来自 import.meta.env.MODE'

/** ===== 顶部聚合指标（真实环境请替换成后端） ===== */
const pointsAgg = reactive({
  totalPoints: 823450,
  todayAdded: 18320
})
const cashAgg = reactive({
  withdrawable: 132580.32,
  pendingAmount: 9400
})
const inviteAgg = reactive({
  l1: 12450,
  l2: 38210,
  l3: 96550,
  teamPointsToday: 26800
})

/** ===== 明细与时间序列存储 ===== */
const recentWithdraws = ref([])
const recentAdLogs = ref([])
const topInviters = ref([])

const seriesStore = reactive({
  withdrawDaily: [],
  withdrawMethodToday: [],
  withdrawStatusToday: [],
  adDaily: [],
  adChannelToday: [],
  invite7dFunnel: [],
  teamPoints14d: []
})

/** ===== 模拟接口（替换为你的后端） ===== */
const mockApi = {
  fetchWithdrawDaily () {
    const out = []
    for (let i = 0; i < 30; i += 1) {
      const date = day(-29 + i)
      const amount = +(5000 + Math.random() * 15000).toFixed(2)
      out.push({ date, amount })
    }
    return out
  },
  fetchWithdrawMethodToday () {
    return [
      { name: '微信', value: 46 },
      { name: '支付宝', value: 31 },
      { name: '银行卡', value: 19 },
      { name: '其它', value: 4 }
    ]
  },
  fetchWithdrawStatusToday () {
    return [
      { name: '待审核', value: 28 },
      { name: '处理中', value: 12 },
      { name: '成功', value: 240 },
      { name: '失败', value: 6 }
    ]
  },
  fetchRecentWithdraws () {
    const out = []
    for (let i = 0; i < 20; i += 1) {
      out.push({
        id: i + 1,
        user: `U${1000 + i}`,
        amount: +(50 + Math.random() * 800).toFixed(2),
        method: ['微信', '支付宝', '银行卡'][i % 3],
        status: ['成功', '处理中', '待审核'][i % 3],
        time: timeAgo(Math.random() * 36)
      })
    }
    return out
  },
  fetchAdDaily () {
    const out = []
    for (let i = 0; i < 30; i += 1) {
      out.push({
        date: day(-29 + i),
        imp: Math.round(2000 + Math.random() * 3000),
        view: Math.round(1200 + Math.random() * 1600),
        points: Math.round(600 + Math.random() * 900)
      })
    }
    return out
  },
  fetchAdChannelToday () {
    return [
      { name: '网盟A', value: 5200 },
      { name: '渠道B', value: 3400 },
      { name: '渠道C', value: 1800 },
      { name: '渠道D', value: 1200 }
    ]
  },
  fetchRecentAdLogs () {
    const out = []
    for (let i = 0; i < 20; i += 1) {
      out.push({
        id: i + 1,
        user: `U${2300 + i}`,
        points: 10 + Math.floor(Math.random() * 30),
        channel: ['A', 'B', 'C'][i % 3],
        videoId: `VID${10000 + i}`,
        time: timeAgo(Math.random() * 24)
      })
    }
    return out
  },
  fetchTopInviters () {
    const out = []
    for (let i = 0; i < 10; i += 1) {
      out.push({
        id: i + 1,
        name: `推广员_${i + 1}`,
        l1: 50 + Math.floor(Math.random() * 400),
        l2: 100 + Math.floor(Math.random() * 600),
        l3: 300 + Math.floor(Math.random() * 1200),
        teamPts7d: 3000 + Math.floor(Math.random() * 20000),
        firstBuyRate: 0.15 + Math.random() * 0.35
      })
    }
    return out
  },
  fetchInvite7dFunnel () {
    const names = ['邀请', '注册', '观看', '完播', '首购']
    const vals = [12000, 8200, 6100, 4700, 1800]
    const out = []
    for (let i = 0; i < names.length; i += 1) {
      out.push({ name: names[i], value: vals[i] })
    }
    return out
  },
  fetchTeamPoints14d () {
    const out = []
    for (let i = 0; i < 14; i += 1) {
      out.push({
        date: day(-13 + i),
        l1: 500 + Math.round(Math.random() * 900),
        l2: 800 + Math.round(Math.random() * 1100),
        l3: 1200 + Math.round(Math.random() * 1500)
      })
    }
    return out
  }
}

function day (offset = 0) {
  const d = new Date()
  d.setDate(d.getDate() + offset)
  const p = (n) => String(n).padStart(2, '0')
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
}

function timeAgo (h) {
  const d = new Date(Date.now() - h * 3600 * 1000)
  return d.toLocaleString('zh-CN', {
    hour12: false
  })
}

/** ===== 图表实例 ===== */
const withdrawTrendRef = ref(null)
const withdrawMethodRef = ref(null)
const withdrawStatusRef = ref(null)
const adPointsTrendRef = ref(null)
const adChannelRef = ref(null)
const adFunnelRef = ref(null)
const inviteStackRef = ref(null)
const inviteFunnelRef = ref(null)
const teamPointsRef = ref(null)

const charts = {}
let ro = null

function initCharts () {
  const pick = (el) => (el ? el.querySelector('.chart') : null)
  const make = (el) => {
    const dom = pick(el)
    return dom ? echarts.init(dom) : null
  }

  charts.wdTrend = make(withdrawTrendRef.value)
  charts.wdMethod = make(withdrawMethodRef.value)
  charts.wdStatus = make(withdrawStatusRef.value)
  charts.adTrend = make(adPointsTrendRef.value)
  charts.adCh = make(adChannelRef.value)
  charts.adFun = make(adFunnelRef.value)
  charts.invStack = make(inviteStackRef.value)
  charts.invFun = make(inviteFunnelRef.value)
  charts.teamPts = make(teamPointsRef.value)

  renderWithdrawCharts()
  renderAdTrend()
  renderAdChannel()
  renderAdFunnel()
  renderInviteCharts()

  ro = new ResizeObserver(() => {
    const arr = Object.values(charts)
    for (let i = 0; i < arr.length; i += 1) {
      const c = arr[i]
      if (c && c.resize) {
        c.resize()
      }
    }
  })

  const screenEl = document.querySelector('.screen')
  if (screenEl) {
    ro.observe(screenEl)
  }
}

function renderWithdrawCharts () {
  if (!charts.wdTrend || !charts.wdMethod || !charts.wdStatus) {
    return
  }
  const d1 = seriesStore.withdrawDaily
  charts.wdTrend.setOption({
    backgroundColor: 'transparent',
    grid: {
      left: 40,
      right: 20,
      top: 30,
      bottom: 30
    },
    tooltip: {
      trigger: 'axis'
    },
    xAxis: {
      type: 'category',
      data: d1.map((x) => x.date),
      axisLabel: {
        color: '#9bb0d3'
      }
    },
    yAxis: {
      type: 'value',
      axisLabel: {
        color: '#9bb0d3'
      },
      splitLine: {
        lineStyle: {
          color: 'rgba(255,255,255,0.06)'
        }
      }
    },
    series: [
      {
        type: 'line',
        smooth: true,
        areaStyle: {
          opacity: 0.2
        },
        data: d1.map((x) => x.amount)
      }
    ]
  })

  charts.wdMethod.setOption({
    backgroundColor: 'transparent',
    legend: {
      bottom: 0,
      textStyle: {
        color: '#9bb0d3'
      }
    },
    tooltip: {
      trigger: 'item'
    },
    series: [
      {
        type: 'pie',
        radius: [
          '40%',
          '70%'
        ],
        data: seriesStore.withdrawMethodToday,
        label: {
          color: '#cfe7ff'
        }
      }
    ]
  })

  charts.wdStatus.setOption({
    backgroundColor: 'transparent',
    grid: {
      left: 40,
      right: 10,
      top: 20,
      bottom: 25
    },
    xAxis: {
      type: 'category',
      data: seriesStore.withdrawStatusToday.map((x) => x.name),
      axisLabel: {
        color: '#9bb0d3'
      }
    },
    yAxis: {
      type: 'value',
      axisLabel: {
        color: '#9bb0d3'
      },
      splitLine: {
        lineStyle: {
          color: 'rgba(255,255,255,0.06)'
        }
      }
    },
    series: [
      {
        type: 'bar',
        data: seriesStore.withdrawStatusToday.map((x) => x.value),
        barWidth: 12,
        label: {
          show: true,
          position: 'top',
          color: '#cfe7ff'
        }
      }
    ]
  })
}

function renderAdTrend () {
  if (!charts.adTrend) {
    return
  }
  const d2 = seriesStore.adDaily
  charts.adTrend.setOption({
    backgroundColor: 'transparent',
    grid: {
      left: 40,
      right: 20,
      top: 30,
      bottom: 30
    },
    tooltip: {
      trigger: 'axis'
    },
    legend: {
      data: [
        '曝光',
        '完播',
        '积分'
      ],
      textStyle: {
        color: '#cfe7ff'
      }
    },
    xAxis: {
      type: 'category',
      data: d2.map((x) => x.date),
      axisLabel: {
        color: '#9bb0d3'
      }
    },
    yAxis: [
      {
        type: 'value',
        name: '次数',
        axisLabel: {
          color: '#9bb0d3'
        },
        splitLine: {
          lineStyle: {
            color: 'rgba(255,255,255,0.06)'
          }
        }
      },
      {
        type: 'value',
        name: '积分',
        axisLabel: {
          color: '#9bb0d3'
        },
        splitLine: {
          show: false
        }
      }
    ],
    series: [
      {
        name: '曝光',
        type: 'line',
        smooth: true,
        data: d2.map((x) => x.imp)
      },
      {
        name: '完播',
        type: 'line',
        smooth: true,
        data: d2.map((x) => x.view)
      },
      {
        name: '积分',
        type: 'bar',
        yAxisIndex: 1,
        barWidth: 10,
        data: d2.map((x) => x.points)
      }
    ]
  })
}

function renderAdChannel () {
  if (!charts.adCh) {
    return
  }
  charts.adCh.setOption({
    backgroundColor: 'transparent',
    grid: {
      left: 60,
      right: 20,
      top: 20,
      bottom: 25
    },
    tooltip: {},
    xAxis: {
      type: 'category',
      data: seriesStore.adChannelToday.map((x) => x.name),
      axisLabel: {
        color: '#9bb0d3',
        interval: 0,
        rotate: 20
      }
    },
    yAxis: {
      type: 'value',
      axisLabel: {
        color: '#9bb0d3'
      },
      splitLine: {
        lineStyle: {
          color: 'rgba(255,255,255,0.06)'
        }
      }
    },
    series: [
      {
        type: 'bar',
        data: seriesStore.adChannelToday.map((x) => x.value),
        barWidth: 12,
        label: {
          show: true,
          position: 'top',
          color: '#cfe7ff'
        }
      }
    ]
  })
}

function renderAdFunnel () {
  if (!charts.adFun) {
    return
  }
  const d2 = seriesStore.adDaily
  const last = d2[d2.length - 1] || {
    imp: 0,
    view: 0,
    points: 0
  }
  const funnelNames = [
    '曝光',
    '观看',
    '完播',
    '计积分'
  ]
  const totalImpToday = seriesStore.adChannelToday.reduce((sum, x) => sum + x.value, 0)
  const funnelVals = [
    totalImpToday,
    Math.round(last.view * 1.2),
    last.view,
    last.points
  ]
  charts.adFun.setOption({
    backgroundColor: 'transparent',
    tooltip: {
      trigger: 'item'
    },
    series: [
      {
        type: 'funnel',
        left: '10%',
        width: '80%',
        label: {
          color: '#cfe7ff'
        },
        data: funnelNames.map((n, i) => ({
          name: n,
          value: funnelVals[i]
        }))
      }
    ]
  })
}

function renderInviteCharts () {
  if (!charts.invStack || !charts.invFun || !charts.teamPts) {
    return
  }

  charts.invStack.setOption({
    backgroundColor: 'transparent',
    grid: {
      left: 50,
      right: 20,
      top: 30,
      bottom: 30
    },
    tooltip: {},
    xAxis: {
      type: 'category',
      data: [
        'L1',
        'L2',
        'L3'
      ],
      axisLabel: {
        color: '#9bb0d3'
      }
    },
    yAxis: {
      type: 'value',
      axisLabel: {
        color: '#9bb0d3'
      },
      splitLine: {
        lineStyle: {
          color: 'rgba(255,255,255,0.06)'
        }
      }
    },
    series: [
      {
        type: 'bar',
        data: [
          inviteAgg.l1,
          inviteAgg.l2,
          inviteAgg.l3
        ],
        barWidth: 14,
        label: {
          show: true,
          position: 'top',
          color: '#cfe7ff'
        }
      }
    ]
  })

  charts.invFun.setOption({
    backgroundColor: 'transparent',
    tooltip: {
      trigger: 'item'
    },
    series: [
      {
        type: 'funnel',
        left: '10%',
        width: '80%',
        label: {
          color: '#cfe7ff'
        },
        data: seriesStore.invite7dFunnel
      }
    ]
  })

  const tp = seriesStore.teamPoints14d
  charts.teamPts.setOption({
    backgroundColor: 'transparent',
    grid: {
      left: 50,
      right: 20,
      top: 30,
      bottom: 30
    },
    tooltip: {
      trigger: 'axis'
    },
    legend: {
      textStyle: {
        color: '#cfe7ff'
      },
      data: [
        'L1',
        'L2',
        'L3'
      ]
    },
    xAxis: {
      type: 'category',
      data: tp.map((x) => x.date),
      axisLabel: {
        color: '#9bb0d3'
      }
    },
    yAxis: {
      type: 'value',
      axisLabel: {
        color: '#9bb0d3'
      },
      splitLine: {
        lineStyle: {
          color: 'rgba(255,255,255,0.06)'
        }
      }
    },
    series: [
      {
        name: 'L1',
        type: 'bar',
        stack: 'pts',
        data: tp.map((x) => x.l1)
      },
      {
        name: 'L2',
        type: 'bar',
        stack: 'pts',
        data: tp.map((x) => x.l2)
      },
      {
        name: 'L3',
        type: 'bar',
        stack: 'pts',
        data: tp.map((x) => x.l3)
      }
    ]
  })
}

/** ===== 拉取并渲染 ===== */
async function fetchAll () {
  seriesStore.withdrawDaily = mockApi.fetchWithdrawDaily()
  seriesStore.withdrawMethodToday = mockApi.fetchWithdrawMethodToday()
  seriesStore.withdrawStatusToday = mockApi.fetchWithdrawStatusToday()
  recentWithdraws.value = mockApi.fetchRecentWithdraws()

  seriesStore.adDaily = mockApi.fetchAdDaily()
  seriesStore.adChannelToday = mockApi.fetchAdChannelToday()
  recentAdLogs.value = mockApi.fetchRecentAdLogs()

  topInviters.value = mockApi.fetchTopInviters()
  seriesStore.invite7dFunnel = mockApi.fetchInvite7dFunnel()
  seriesStore.teamPoints14d = mockApi.fetchTeamPoints14d()

  renderWithdrawCharts()
  renderAdTrend()
  renderAdChannel()
  renderAdFunnel()
  renderInviteCharts()
}

/** ===== 健康态（展示） ===== */
const apiHealthy = ref(true)
const redisHealthy = ref(true)

/** ===== 工具 ===== */
function formatNum (n) {
  const can = n && typeof n.toLocaleString === 'function'
  return can ? n.toLocaleString('zh-CN') : String(n)
}

/** ===== 生命周期 ===== */
let clockId = null

onMounted(() => {
  document.documentElement.style.setProperty(
    '--app-bg',
    bg
  )
  applyTheme()
  tickClock()
  clockId = setInterval(tickClock, 1000)
  initCharts()
  fetchAll()
  document.addEventListener('fullscreenchange', onFsChange)
})

onBeforeUnmount(() => {
  if (clockId) {
    clearInterval(clockId)
  }
  if (ro) {
    ro.disconnect()
  }
  document.removeEventListener('fullscreenchange', onFsChange)
  const arr = Object.values(charts)
  for (let i = 0; i < arr.length; i += 1) {
    const c = arr[i]
    if (c && c.dispose) {
      c.dispose()
    }
  }
})
</script>

<style scoped>
/* 容器 */
.screen-wrapper {
  min-height: 100vh;
  padding: clamp(8px, 1.5vw, 1px);
  background: var(--app-bg);
}

.screen {
  margin: 0 auto;
  max-width: 1600px;
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: clamp(8px, 1vw, 1px);
}

/* 顶部栏 */
.topbar {
  display: flex;
  /* grid-template-columns: 1fr auto 1fr; */
  align-items: center;

  border-radius: 16px;
  padding: clamp(8px, 1vw, 12px) clamp(10px, 1.2vw, 16px);
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4) inset;
  position: sticky;
  top: 0;
  z-index: 5;
  backdrop-filter: blur(6px);
}

.topbar-left {
  display: flex;
  gap: 40px;
  flex-direction: row;
}

.brand {
  font-weight: 700;
  letter-spacing: 1px;
  color: #e6f0ff;
  font-size: clamp(16px, 1.5vw, 20px);
}

.brand-left {
  display: flex;
  margin-right: 10px;
  flex-direction: column;
}

.env-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  color: #cfe7ff;
  font-size: 12px;
}

.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #7c6cff;
  box-shadow: 0 0 8px currentColor;
}

.dot.dev {
  background: #ffb703;
}

.dot.test {
  background: #06d6a0;
}

.dot.staging {
  background: #118ab2;
}

.dot.prod {
  background: #00d4ff;
}

.topbar-center {
  display: flex;
  justify-content: center;
}

.clock {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
  color: #a6c8ff;
  letter-spacing: 1px;
  font-size: clamp(12px, 1.3vw, 16px);
}

.topbar-right {
  display: flex;
  justify-content: flex-end;
  gap: clamp(8px, 1vw, 16px);
  align-items: center;
  flex-wrap: wrap;
}

.kpis {
  display: flex;
  gap: clamp(8px, 1vw, 16px);
  flex-wrap: wrap;
}

.kpi {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
  border: 1px solid rgba(255, 255, 255, 0.08);
  padding: 8px 12px;
  border-radius: 12px;
  min-width: 220px;
}

.kpi-label {
  font-size: 12px;
  color: #9bb0d3;
}

.kpi-value {
  font-size: clamp(18px, 2.2vw, 24px);
  font-weight: 700;
  margin-top: 6px;
  color: #e7f2ff;
}

.kpi-sub {
  font-size: 12px;
  margin-top: 4px;
  color: #9bb0d3;
}

/* 网格 */
.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: minmax(260px, 32vh);
  gap: clamp(8px, 1vw, 12px);
}

.panel {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.02));
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  padding: 10px;
  display: grid;
  grid-template-rows: 34px 1fr;
  overflow: hidden;
  grid-column: span 6;
}

.panel .panel-title {
  color: #cfe7ff;
  font-size: 14px;
  letter-spacing: 0.5px;
  display: flex;
  align-items: center;
  padding: 0 6px;
}

.panel .chart {
  width: 100%;
  height: 100%;
}

.span-2 {
  grid-column: span 12;
}

@media (max-width: 1199px) {
  .panel {
    grid-column: span 6;
  }

  .span-2 {
    grid-column: span 12;
  }
}

@media (max-width: 899px) {

  .panel,
  .span-2 {
    grid-column: span 12;
  }
}

/* 表格 */
.table {
  display: grid;
  grid-template-rows: auto 1fr;
  overflow: hidden;
}

.table .thead,
.table .row {
  display: grid;
  grid-template-columns: 40px 1fr 140px 120px 160px;
  column-gap: 12px;
  align-items: center;
  padding: 6px 10px;
}

.table.mini .thead,
.table.mini .row {
  grid-template-columns: 1fr 100px 100px 100px 160px;
}

.table .thead {
  background: rgba(255, 255, 255, 0.06);
  color: #9bb0d3;
  font-size: 12px;
  position: sticky;
  top: 0;
  z-index: 1;
}

.table .tbody {
  overflow: auto;
}

.table .row {
  border-bottom: 1px dashed rgba(255, 255, 255, 0.06);
  color: #eaf2ff;
  font-size: 13px;
}

.table .row .sku {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 底部栏 */
.bottombar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-radius: 14px;
  padding: 8px 12px;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: #9bb0d3;
  font-size: 12px;
  margin-top: clamp(8px, 1vw, 12px);
}

.status {
  display: flex;
  gap: 10px;
  align-items: center;
}

.pill {
  padding: 4px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.pill.ok {
  border-color: rgba(20, 241, 149, 0.5);
}

.pill.warn {
  border-color: rgba(255, 209, 102, 0.5);
}

.pill.info {
  border-color: rgba(0, 212, 255, 0.5);
}

/* 滚动条 */
.table .tbody::-webkit-scrollbar {
  width: 6px;
}

.table .tbody::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.12);
  border-radius: 999px;
}
</style>
